﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Globalization;
using Chefbook.HelperMethods;

namespace Chefbook.DataObjects.XmlSerialization
{
    /// <summary>
    /// Static class for serializing and deserializing categories
    /// </summary>
    public static class XmlSerializer
    {
        private const string Category = "Category";
        private const string Root = "Root";
        private const string Recipe = "Recipe";
        private const string Description = "Description";
        private const string Directions = "Directions";
        private const string PrepTime = "PrepTime";
        private const string CookTime = "CookTime";
        private const string Ingredients = "Ingredients";
        private const string Ingredient = "Ingredient";
        private const string Name = "Name";
        private const string Amount = "Amount";
        private const string Units = "Units";

        
        /// <summary>
        /// Serializes all categories given a list of root categories
        /// </summary>
        /// <param name="rootCategories">IEnumerable of categories that have no parent</param>
        /// <returns>An XDocument representing the category data</returns>
        public static void SerializeCategories(IEnumerable<Category> rootCategories, out XDocument serializedCategories, out XDocument serializedRecipes)
        {
            List<Recipe> recipeList = new List<Recipe>();

            XElement rootXml = new XElement(Root);
            foreach (var item in rootCategories)
            {
                rootXml.Add(SerializeSubs(item, recipeList));
            }
            serializedCategories = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"));
            serializedCategories.Add(rootXml);

            serializedRecipes = SerializeRecipes(recipeList);

        }

        /// <summary>
        /// Deserializes all categories from an XDocument
        /// </summary>
        /// <param name="xml">The XDocument to deserialize from</param>
        /// <returns>An IEnumerable of all the root categories</returns>
        public static IEnumerable<Category> DeserializeCategories(XDocument xmlCategories, XDocument xmlRecipes)
        {
            List<Category> rootCategories = new List<Category>();
            Dictionary<string, Recipe> allRecipes = new Dictionary<string, Recipe>();
            foreach (var recipe in DeserializeRecipes(xmlRecipes))
            {
                allRecipes.Add(recipe.Name, recipe);
            } 
            var xmlcategorylist = from category in xmlCategories.Root.Elements(Category)
                    select category;
            foreach (var xmlCat in xmlcategorylist)
            {
                rootCategories.Add(DeserializeSubs(xmlCat, null, allRecipes));
            }
            return rootCategories;
        }

        private static Category DeserializeSubs(XElement element, Category parent, Dictionary<string, Recipe> allRecipes)
        {
            Category thisCat = new Category(parent) { Name = element.Attribute(Name).Value };
            foreach (var recipeXml in element.Elements(Recipe))
            {
                thisCat.AddRecipe(allRecipes[recipeXml.Attribute(Name).Value]);
            }
            foreach (var categoryXml in element.Elements(Category))
            {
                DeserializeSubs(categoryXml, thisCat, allRecipes);
            }
            return thisCat;
        }

        private static XElement SerializeSubs(Category category, List<Recipe> recipeList)
        {
            XElement catElement = new XElement(Category, new XAttribute(Name, category.Name));
            foreach (var recipe in category.DirectRecipes)
            {
                catElement.Add(
                    new XElement(Recipe, 
                        new XAttribute(Name, recipe.Name)));
                recipeList.AddIfNewAndNotNull(recipe);
            }
            foreach (var sub in category.Subcategories)
            {
                catElement.Add(SerializeSubs(sub, recipeList));
            }
            return catElement;
        }

        #region Recipes
        /// <summary>
        /// Deserializes all recipes in an XDocument
        /// </summary>
        /// <param name="doc">The XDocument to deserialize from</param>
        /// <returns>An IEnumerable of all the recipes</returns>
        public static IEnumerable<Recipe> DeserializeRecipes(XDocument doc)
        {
            List<Recipe> recipes = new List<Recipe>();
            foreach (var xmlRec in doc.Root.Elements(Recipe))
            {
                recipes.Add(DeserializeRecipe(xmlRec));
            }
            return recipes;
        }

        /// <summary>
        /// Serializes all recipes to an XDocument
        /// </summary>
        /// <param name="recipes">An IEnumerable of recipes to serialize</param>
        /// <returns>The XDocument containing the serialized recipes</returns>
        public static XDocument SerializeRecipes(IEnumerable<Recipe> recipes)
        {
            XElement rootXml = new XElement(Root);
            foreach (var item in recipes)
            {
                rootXml.Add(SerializeRecipe(item));
            }
            XDocument xml = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"));
            xml.Add(rootXml);
            return xml;
        }

        private static XElement SerializeRecipe(Recipe recipe)
        {
            XElement recipeXml = new XElement(Recipe);
            recipeXml.Add(new XAttribute(Name, recipe.Name),
                new XElement(Description, recipe.Description),
                new XElement(Directions, recipe.Directions),
                new XElement(CookTime, recipe.CookTime.TotalMinutes),
                new XElement(PrepTime, recipe.PrepTime.TotalMinutes));
            XElement ingredientsXml = new XElement(Ingredients);
            recipeXml.Add(ingredientsXml);
            foreach (var ing in recipe.Ingredients)
            {
                ingredientsXml.Add(new XElement(Ingredient,
                    new XAttribute(Name, ing.Name),
                    new XAttribute(Amount, ing.Amount),
                    new XAttribute(Units, ing.Units)));
            }

            return recipeXml;
        }

        private static Recipe DeserializeRecipe(XElement recipeXml)
        {
            Recipe rec = new Recipe(null);
            rec.Description = recipeXml.Element(Description).Value;
            rec.Directions = recipeXml.Element(Directions).Value;
            rec.CookTime = TimeSpan.FromMinutes(double.Parse(recipeXml.Element(CookTime).Value, CultureInfo.InvariantCulture));
            rec.PrepTime = TimeSpan.FromMinutes(double.Parse(recipeXml.Element(PrepTime).Value, CultureInfo.InvariantCulture));
            rec.Name = recipeXml.Attribute(Name).Value;
            foreach (var ingXml in recipeXml.Element(Ingredients).Elements(Ingredient))
            {
                Ingredient i = new Ingredient(rec);
                i.Amount = float.Parse(ingXml.Attribute(Amount).Value, CultureInfo.InvariantCulture);
                i.Units = ingXml.Attribute(Units).Value;
                i.Name = ingXml.Attribute(Name).Value;
            }
            return rec;
        }
        #endregion
    }
}
